title: 16.Go 编程模式:k8s Visitor 模式 outline: deep

本篇文章主要想讨论一下,Kubernetes 的 kubectl 命令中的使用到到的一个编程模式 – Visitor(注:其实,kubectl 主要使用到了两个一个是Builder,另一个是Visitor)。本来,Visitor 是面向对象设计模英中一个很重要的设计模款(参看Wikipedia Visitor Pattern词条),这个模式是一种将算法与操作对象的结构分离的一种方法。这种分离的实际结果是能够在不修改结构的情况下向现有对象结构添加新操作,是遵循开放/封闭原则的一种方法。这篇文章我们重点看一下 kubelet 中是怎么使用函数式的方法来实现这个模式的。

本文是全系列中第9 / 10篇:Go编程模式

« 上一篇文章下一篇文章 »

目录

一个简单示例

我们还是先来看一个简单设计模式的Visitor的示例。

package main

import ( "encoding/json" "encoding/xml" "fmt" )

type Visitor func(shape Shape)

type Shape interface { accept(Visitor) }

type Circle struct { Radius int }

func (c Circle) accept(v Visitor) { v(c) }

type Rectangle struct { Width, Heigh int }

func (r Rectangle) accept(v Visitor) { v(r) }

然后,我们实现两个Visitor,一个是用来做JSON序列化的,另一个是用来做XML序列化的

func JsonVisitor(shape Shape) { bytes, err := json.Marshal(shape) if err != nil { panic(err) } fmt.Println(string(bytes)) }

func XmlVisitor(shape Shape) { bytes, err := xml.Marshal(shape) if err != nil { panic(err) } fmt.Println(string(bytes)) }

下面是我们的使用Visitor这个模式的代码

func main() { c := Circle{10} r := Rectangle{100, 200} shapes := []Shape{c, r}

for _, s := range shapes { s.accept(JsonVisitor) s.accept(XmlVisitor) }

}

其实,这段代码的目的就是想解耦 数据结构和 算法,使用 Strategy 模式也是可以完成的,而且会比较干净。但是在有些情况下,多个Visitor是来访问一个数据结构的不同部分,这种情况下,数据结构有点像一个数据库,而各个Visitor会成为一个个小应用。 kubectl就是这种情况。

k8s相关背景

接下来,我们再来了解一下相关的知识背景:

kubectl 的代码比较复杂,不过,其本原理简单来说,它从命令行和yaml文件中获取信息,通过Builder模式并把其转成一系列的资源,最后用 Visitor 模式模式来迭代处理这些Reources

下面我们来看看 kubectl 的实现,为了简化,我用一个小的示例来表明 ,而不是直接分析复杂的源码。

kubectl的实现方法

Visitor模式定义

首先,kubectl 主要是用来处理 Info结构体,下面是相关的定义:

type VisitorFunc func(*Info, error) error

type Visitor interface { Visit(VisitorFunc) error }

type Info struct { Namespace   string Name        string OtherThings string } func (info *Info) Visit(fn VisitorFunc) error { return fn(info, nil) }

我们可以看到,

我们再来定义几种不同类型的 Visitor。

Name Visitor

这个Visitor 主要是用来访问 Info 结构中的 NameNameSpace 成员

type NameVisitor struct { visitor Visitor }

func (v NameVisitor) Visit(fn VisitorFunc) error { return v.visitor.Visit(func(info *Info, err error) error { fmt.Println("NameVisitor() before call function") err = fn(info, err) if err == nil { fmt.Printf("==› Name=%s, NameSpace=%s\n", info.Name, info.Namespace) } fmt.Println("NameVisitor() after call function") return err }) }

我们可以看到,上面的代码:

Other Visitor

这个Visitor主要用来访问 Info 结构中的 OtherThings 成员

type OtherThingsVisitor struct { visitor Visitor }

func (v OtherThingsVisitor) Visit(fn VisitorFunc) error { return v.visitor.Visit(func(info *Info, err error) error { fmt.Println("OtherThingsVisitor() before call function") err = fn(info, err) if err == nil { fmt.Printf("==› OtherThings=%s\n", info.OtherThings) } fmt.Println("OtherThingsVisitor() after call function") return err }) }

实现逻辑同上,我就不再重新讲了

Log Visitor

type LogVisitor struct { visitor Visitor }

func (v LogVisitor) Visit(fn VisitorFunc) error { return v.visitor.Visit(func(info *Info, err error) error { fmt.Println("LogVisitor() before call function") err = fn(info, err) fmt.Println("LogVisitor() after call function") return err }) }

使用方代码

现在我们看看如果使用上面的代码:

func main() { info := Info{} var v Visitor = &info v = LogVisitor{v} v = NameVisitor{v} v = OtherThingsVisitor{v}

loadFile := func(info *Info, err error) error { info.Name = "Hao Chen" info.Namespace = "MegaEase" info.OtherThings = "We are running as remote team." return nil } v.Visit(loadFile) }

上面的代码,我们可以看到

上面的代码输出如下的信息,你可以看到代码的执行顺序是怎么执行起来了

LogVisitor() before call function NameVisitor() before call function OtherThingsVisitor() before call function ==› OtherThings=We are running as remote team. OtherThingsVisitor() after call function ==› Name=Hao Chen, NameSpace=MegaEase NameVisitor() after call function LogVisitor() after call function

我们可以看到,上面的代码有以下几种功效:

所以,其实,我们是可以把上面的代码重构一下的。

Visitor修饰器

下面,我们用修饰器模式来重构一下上面的代码。

type DecoratedVisitor struct { visitor Visitor decorators []VisitorFunc }

func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor { if len(fn) == 0 { return v } return DecoratedVisitor{v, fn} }

// Visit implements Visitor func (v DecoratedVisitor) Visit(fn VisitorFunc) error { return v.visitor.Visit(func(info *Info, err error) error { if err != nil { return err } if err := fn(info, nil); err != nil { return err } for i := range v.decorators { if err := v.decorators[i](info, nil); err != nil { return err } } return nil }) }

上面的代码并不复杂,

于是,我们的代码就可以这样运作了:

info := Info{} var v Visitor = &info v = NewDecoratedVisitor(v, NameVisitor, OtherVisitor)

v.Visit(LoadFile)

是不是比之前的那个简单?注意,这个DecoratedVisitor 同样可以成为一个Visitor来使用。

好,上面的这些代码全部存在于 kubectl 的代码中,你看懂了这里面的代码逻辑,相信你也能够看懂 kubectl 的代码了。